/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.drawee.view;

import com.facebook.common.internal.Objects;
import com.facebook.drawee.drawable.Callback;
import com.facebook.drawee.drawable.RootShapeElement;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.interfaces.DraweeHierarchy;
import com.facebook.imagepipeline.systrace.FrescoSystrace;
import com.oszc.bbhmlibrary.androidwrapper.SystemClock;
import com.oszc.bbhmlibrary.utils.LogUtil;
import com.oszc.bbhmlibrary.utils.MainHandler;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.Image;
import ohos.agp.components.element.Element;
import ohos.agp.render.Canvas;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.media.image.PixelMap;
import ohos.multimodalinput.event.TouchEvent;

import javax.annotation.Nullable;

/**
 * View that displays a {@link DraweeHierarchy}.
 *
 * <p>Hierarchy should be set prior to using this view. See {@code setHierarchy}. Because creating a
 * hierarchy is an expensive operation, it is recommended this be done once per view, typically near
 * creation time.
 *
 * <p>In order to display an image, controller has to be set. See {@code setController}.
 *
 * <p>Although ImageView is subclassed instead of subclassing View directly, this class does not
 * support ImageView's setImageXxx, setScaleType and similar methods. Extending ImageView is a short
 * term solution in order to inherit some of its implementation (padding calculations, etc.). This
 * class is likely to be converted to extend View directly in the future, so avoid using ImageView's
 * methods and properties.
 */
public class DraweeView<DH extends DraweeHierarchy> extends Image implements Component.BindStateChangedListener ,Callback {
    private final String TAG = DraweeView.class.getSimpleName();

    private final AspectRatioMeasure.Spec mMeasureSpec = new AspectRatioMeasure.Spec();
    private float mAspectRatio = 0;
    private DraweeHolder<DH> mDraweeHolder;
    private boolean mInitialised = false;
    private boolean mLegacyVisibilityHandlingEnabled = false;

    private static boolean sGlobalLegacyVisibilityHandlingEnabled = false;
    private Matrix mMatrix;
    private Matrix mDrawMatrix;
    private final RectFloat mTempSrc = new RectFloat();
    private final RectFloat mTempDst = new RectFloat();
    private boolean isDirty = true;

    public static void setGlobalLegacyVisibilityHandlingEnabled(
            boolean legacyVisibilityHandlingEnabled) {
        sGlobalLegacyVisibilityHandlingEnabled = legacyVisibilityHandlingEnabled;
    }

    public DraweeView(Context context) {
        super(context);
        init(context);
    }

    public DraweeView(Context context, AttrSet attrs) {
        super(context, attrs);
        init(context);
    }

    public DraweeView(Context context, AttrSet attrs, String defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }


    /**
     * This method is idempotent so it only has effect the first time it's called
     */
    private void init(Context context) {
        try {
            if (FrescoSystrace.isTracing()) {
                FrescoSystrace.beginSection("DraweeView#init");
            }
            if (mInitialised) {
                return;
            }
            mInitialised = true;
            mDraweeHolder = DraweeHolder.create(null, context, this);
            setBindStateChangedListener(this);
            //addDrawTask(this::drawToCanvas);
//      mDraweeHolder.onDraw();
//      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//        ColorStateList imageTintList = getImageTintList();
//        if (imageTintList == null) {
//          return;
//        }
//        setColorFilter(imageTintList.getDefaultColor());
//      }
            mLegacyVisibilityHandlingEnabled =
                    sGlobalLegacyVisibilityHandlingEnabled;
//              && context.getApplicationInfo().targetSdkVersion >= 24; // Build.VERSION_CODES.N
            addDrawTask(this::drawToCanvas);
            setTouchEventListener(this::onTouchEvent);
        } finally {
            if (FrescoSystrace.isTracing()) {
                FrescoSystrace.endSection();
            }
        }

    }

    /**
     * Sets the hierarchy.
     */
    public void setHierarchy(DH hierarchy) {
        //Logutil.error(TAG,"setHierarchy");
        mDraweeHolder.setHierarchy(hierarchy);
        Element topLevelDrawable = mDraweeHolder.getTopLevelDrawable();
        //Logutil.error(TAG,"setHierarchy topLevelDrawable:"+topLevelDrawable);
        setImageElement(topLevelDrawable);
    }

    public void onChange(Element element) {
        isDirty = true;
        invalidate();
    }

    @Override
    public void setLayoutConfig(ComponentContainer.LayoutConfig config) {
        isDirty = true;
        super.setLayoutConfig(config);
        invalidate();
    }

    /**
     * Gets the hierarchy if set, throws NPE otherwise.
     */
    public DH getHierarchy() {
        return mDraweeHolder.getHierarchy();
    }

    /**
     * Returns whether the hierarchy is set or not.
     */
    public boolean hasHierarchy() {
        return mDraweeHolder.hasHierarchy();
    }

    /**
     * Gets the top-level drawable if hierarchy is set, null otherwise.
     */
    @Nullable
    public Element getTopLevelDrawable() {
        return mDraweeHolder.getTopLevelDrawable();
    }

    /**
     * Sets the controller.
     */
    public void setController(@Nullable DraweeController draweeController) {
        mDraweeHolder.setController(draweeController);
        setImageElement(mDraweeHolder.getTopLevelDrawable());
    }

    @Override
    public void setImageElement(Element element) {
        LogUtil.error(TAG,"canvas setImageElement width" + element.getWidth()
                + "; height = " + element.getHeight()+" element:"+element.getClass().getSimpleName());
        //configBounds(element);
        super.setImageElement(element);
        //invalidate();
        element.setCallback(this::onChange);
        if(element instanceof RootShapeElement){
            LogUtil.error(TAG,"setImageElement set hmcallback");
            ((RootShapeElement) element).setHmCallback(this);
        }
    }

  /*
  public void configBounds(){
    if(getImageElement()!=null) {
      configBounds(getImageElement());
    }
  }
   */

    //the framework lack of the ability of configure bounds
    private void configBounds(Element element) {
        if (element == null) {
            return;
        }
        int dwidth = element.getWidth();
        int dheight = element.getHeight();
        if (element instanceof RootShapeElement) {
            dwidth = ((RootShapeElement) element).getIntrinsicWidth();
            dheight = ((RootShapeElement) element).getIntrinsicHeight();
        }

        int vwidth = getWidth() - getPaddingLeft() - getPaddingRight();
        int vheight = getHeight() - getPaddingTop() - getPaddingBottom();
        //Logutil.error(TAG,"configBounds A dwidht:"+dwidth+"  dheight:"+dheight+"   vwidth:"+vwidth+"   vheight:"+vheight);

        final boolean fits = (dwidth < 0 || vwidth == dwidth)
                && (dheight < 0 || vheight == dheight);

        ScaleMode mScaleType = getScaleMode();
        if (dwidth <= 0 || dheight <= 0 || ScaleMode.STRETCH == mScaleType) {
            //Logutil.error(TAG,"configBounds B");
            element.setBounds(0, 0, vwidth, vheight);
            mDrawMatrix = null;
        } else {
            // We need to do the scaling ourself, so have the drawable
            // use its native size.
            //Logutil.error(TAG,"configBounds C");
            element.setBounds(0, 0, dwidth, dheight);

            if (fits) {
                // The bitmap fits exactly, no transform needed.
                element.setBounds(0, 0, vwidth, vheight);
                mDrawMatrix = null;
                //Logutil.error(TAG,"configBounds D");
            } else if (ScaleMode.CENTER == mScaleType) {
                //Logutil.error(TAG,"configBounds E");
                mDrawMatrix = mMatrix;
                mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f),
                        Math.round((vheight - dheight) * 0.5f));

            } else if (ScaleMode.CLIP_CENTER == mScaleType) {
                //Logutil.error(TAG,"configBounds F");
                float scale;
                float dx = 0, dy = 0;
                if (dwidth * vheight > vwidth * dheight) {
                    scale = (float) vheight / (float) dheight;
                    dx = (vwidth - dwidth * scale) * 0.5f;
                } else {
                    scale = (float) vwidth / (float) dwidth;
                    dy = (vheight - dheight * scale) * 0.5f;
                }

                mDrawMatrix.setScale(scale, scale);
                mDrawMatrix.postTranslate(Math.round(dx), Math.round(dy));
            }
      /*
      else if (ScaleMode.CENTER== mScaleType) {
        mDrawMatrix = mMatrix;
        float scale;
        float dx;
        float dy;
        if (dwidth <= vwidth && dheight <= vheight) {
          scale = 1.0f;
        } else {
          scale = Math.min((float) vwidth / (float) dwidth,
                  (float) vheight / (float) dheight);
        }
        dx = Math.round((vwidth - dwidth * scale) * 0.5f);
        dy = Math.round((vheight - dheight * scale) * 0.5f);
        mDrawMatrix.setScale(scale, scale);
        mDrawMatrix.postTranslate(dx, dy);
      } else {
        // Generate the required transform.
        //mTempSrc.fuse(0, 0, dwidth, dheight);

          setRectFloat(mTempSrc,0,0,dwidth,dheight);
          setRectFloat(mTempDst,0,0,vwidth,vheight);
        mDrawMatrix = mMatrix;
        mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
      }
       */
        }
        isDirty = false;
    }

    private void drawToCanvas(Component component, Canvas canvas) {
        if (isDirty) {
            configBounds(getImageElement());
        }
        canvas.save();
        canvas.translate(getPaddingLeft(), getPaddingTop());
        getImageElement().drawToCanvas(canvas);
        canvas.restore();
    }

    private void setRectFloat(RectFloat rectFloat, int l, int t, int r, int b) {
        rectFloat.left = l;
        rectFloat.top = t;
        rectFloat.right = r;
        rectFloat.bottom = b;
    }

    /**
     * Gets the controller if set, null otherwise.
     */
    @Nullable
    public DraweeController getController() {
        return mDraweeHolder.getController();
    }

    /**
     * Returns whether the controller is set or not.
     */
    public boolean hasController() {
        return mDraweeHolder.getController() != null;
    }

    @Override
    public boolean isBoundToWindow() {
        maybeOverrideVisibilityHandling();
        onAttach();
        return super.isBoundToWindow();
    }

    @Override
    public void onComponentBoundToWindow(Component component) {
        maybeOverrideVisibilityHandling();
        onAttach();

    }

    @Override
    public void onComponentUnboundFromWindow(Component component) {
        maybeOverrideVisibilityHandling();
        onDetach();

    }
//
//  @Override
//  protected void onAttachedToWindow() {
//    super.onAttachedToWindow();
//    maybeOverrideVisibilityHandling();
//    onAttach();
//  }

//  @Override
//  protected void onDetachedFromWindow() {
//    super.onDetachedFromWindow();
//    maybeOverrideVisibilityHandling();
//    onDetach();
//  }

//  @Override
//  public void onStartTemporaryDetach() {
//    super.onStartTemporaryDetach();
//    maybeOverrideVisibilityHandling();
//    onDetach();
//  }
//
//  @Override
//  public void onFinishTemporaryDetach() {
//    super.onFinishTemporaryDetach();
//    maybeOverrideVisibilityHandling();
//    onAttach();
//  }

    /**
     * Called by the system to attach. Subclasses may override.
     */
    protected void onAttach() {
        doAttach();
    }

    /**
     * Called by the system to detach. Subclasses may override.
     */
    protected void onDetach() {
        doDetach();
    }

    /**
     * Does the actual work of attaching.
     *
     * <p>Non-test subclasses should NOT override. Use onAttach for custom code.
     */
    protected void doAttach() {
        mDraweeHolder.onAttach();
    }

    /**
     * Does the actual work of detaching.
     *
     * <p>Non-test subclasses should NOT override. Use onDetach for custom code.
     */
    protected void doDetach() {
        mDraweeHolder.onDetach();
    }


    public boolean onTouchEvent(Component component, TouchEvent event) {
        if (mDraweeHolder.onTouchEvent(event)) {
            return true;
        }
        return false;
    }

    /**
     * Use this method only when using this class as an ordinary ImageView.
     *
     * @deprecated Use {@link #setController(DraweeController)} instead.
     */
    @Deprecated
    public void setImageDrawable(Element drawable) {
        init(getContext());
        mDraweeHolder.setController(null);
        super.setImageElement(drawable);
    }

    /**
     * Use this method only when using this class as an ordinary ImageView.
     *
     * @deprecated Use {@link #setController(DraweeController)} instead.
     */
//  @Override
    @Deprecated
    public void setImageBitmap(PixelMap bm) {
        init(getContext());
        mDraweeHolder.setController(null);
        super.setPixelMap(bm);
    }

    /**
     * Use this method only when using this class as an ordinary ImageView.
     *
     * @deprecated Use {@link #setController(DraweeController)} instead.
     */
    @Deprecated
    public void setImageResource(int resId) {
        init(getContext());
        mDraweeHolder.setController(null);
        super.setPixelMap(resId);
    }

//  /**
//   * Use this method only when using this class as an ordinary ImageView.
//   *
//   * @deprecated Use {@link #setController(DraweeController)} instead.
//   */
//  @Override
//  @Deprecated
//  public void setImageURI(Uri uri) {
//    init(getContext());
//    mDraweeHolder.setController(null);
//    super.setImageURI(uri);
//  }

    /**
     * Sets the desired aspect ratio (w/h).
     */
    public void setAspectRatio(float aspectRatio) {
        //Logutil.error(TAG,"setAspectRatio A");
        if (aspectRatio == mAspectRatio) {
            //Logutil.error(TAG,"setAspectRatio B");
            return;
        }
        //Logutil.error(TAG,"setAspectRatio C");
        mAspectRatio = aspectRatio;
        postLayout();
    }

    /**
     * Gets the desired aspect ratio (w/h).
     */
    public float getAspectRatio() {
        return mAspectRatio;
    }

    public void setLegacyVisibilityHandlingEnabled(boolean legacyVisibilityHandlingEnabled) {
        mLegacyVisibilityHandlingEnabled = legacyVisibilityHandlingEnabled;
    }

    @Override
    public void postLayout() {
        //Logutil.error(TAG,"postLayout");
        mMeasureSpec.width = getWidth();
        mMeasureSpec.height =getHeight();
        AspectRatioMeasure.updateMeasureSpec(
                mMeasureSpec,
                mAspectRatio,
                getLayoutConfig(),
                getPaddingLeft() + getPaddingRight(),
                getPaddingTop() + getPaddingBottom());
//    super.onMeasure(mMeasureSpec.width, mMeasureSpec.height);
        maybeOverrideVisibilityHandling();
        super.postLayout();
        //todo ???
        //setWidth(mMeasureSpec.width);
        //setHeight(mMeasureSpec.height);
        ComponentContainer.LayoutConfig layoutConfig = getLayoutConfig();
        layoutConfig.width = mMeasureSpec.width;
        layoutConfig.height = mMeasureSpec.height;
        setLayoutConfig(layoutConfig);
    }

    //  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mMeasureSpec.width = widthMeasureSpec;
        mMeasureSpec.height = heightMeasureSpec;
        AspectRatioMeasure.updateMeasureSpec(
                mMeasureSpec,
                mAspectRatio,
                getLayoutConfig(),
                getPaddingLeft() + getPaddingRight(),
                getPaddingTop() + getPaddingBottom());
        //super.onMeasure(mMeasureSpec.width, mMeasureSpec.height);
    }

    //  @Override
    protected void onVisibilityChanged(Component changedView, int visibility) {
        //super.onVisibilityChanged(changedView, visibility);
        maybeOverrideVisibilityHandling();
    }


    private void maybeOverrideVisibilityHandling() {
        //Logutil.error(TAG,"maybeOverrideVisibilityHandling A");
        if (mLegacyVisibilityHandlingEnabled) {
            Element drawable = getImageElement();
            if (drawable != null) {
                //Logutil.error(TAG,"maybeOverrideVisibilityHandling B  getVisibility:"+(getVisibility()==VISIBLE));
                drawable.setVisible(getVisibility() == VISIBLE, false);
            }
        }
    }

    @Override
    public String toString() {
        return Objects.toStringHelper(this)
                .add("holder", mDraweeHolder != null ? mDraweeHolder.toString() : "<no holder set>")
                .toString();
    }

    @Override
    public void invalidateDrawable(Element who) {
        //LogUtil.error(TAG,"canvas invalidateDrawable ");
        onChange(who);

    }

    @Override
    public void scheduleDrawable(Element who, Runnable what, long when) {
        //LogUtil.error(TAG,"canvas scheduleDrawable A");
        final long delay = when - SystemClock.uptimeMillis();
        if(what!=null&& delay > 0) {
            //LogUtil.error(TAG,"canvas scheduleDrawable B "+ when+ "   delay:"+delay);
            MainHandler.postDelay(what, delay);
        }else{
            //LogUtil.error(TAG,"canvas scheduleDrawable C");
        }
    }

    @Override
    public void unscheduleDrawable(Element who, Runnable what) {
        //LogUtil.error(TAG,"canvas unscheduleDrawable ");
        MainHandler.removeTask(what);
    }
}
